package com.cskaoyan.javase._2inner._6lambda._0introduction;

/**
 * Lambda表达式的引入:
 *
 * Lambda 表达式是JDK8的一个新特性,可以取代接口的匿名内部类,写出更优雅的Java代码。
 * 如果说匿名内部类实际上是局部内部类的更进一步，简化了局部内部类，
 * 那么Lambda就是匿名内部类更进一步，语法上更简洁了，代码更优雅了，是高端的玩法，"是人上人的玩法"。
 *
 * 以上描述总体就说了三点：
 *      1.Lambda表达式仍然是局部内部类，是特殊的局部内部类，仍然定义在局部位置。
 *          那么局部内部类的注意事项，也一样存在。
 *          当然Lambda表达式肯定和局部内部类,匿名内部类有很大的区别.
 *      2.Lambda表达式在取代匿名内部类时，不是全部都取代，而是取代接口的匿名内部类
 *          而类的匿名内部类Lambda表达式是不能取代的。
 *      3.Lambda表达式是匿名内部类的更进一步
 *          Lambda表达式得到的也不是一个类，而是一个对象，并且是接口的子类对象。
 *
 * 以上Lambda表达式不过是简化接口的匿名内部类,仍然是创建了子类对象,创建的是接口的子类对象
 *
 * 使用Lambda表达式首先需要一个接口,因为Lambda表达式是创建接口的子类对象
 * 但是Lambda表达式不是任何接口,都能创建它的子类对象
 * Lambda表达式对接口有特殊的要求,要求:
 *      接口必须有且仅有一个要强制子类实现的抽象方法.
 * 在Java当中,把这种"有且仅有一个要强制子类实现的抽象方法"的接口称之为"功能接口(Functional Interface)"
 * 在语法上,类似于注解"@Override",使用注解"@FunctionalInterface"标记某个接口,
 *      如果语法不报错,那么该接口就是一个功能接口
 * 思考:
 *      1.功能接口中必须只有一个方法吗?
 *          不是,因为Java8中存在默认实现方法或者静态实现方法,不需要子类实现.
 *
 *      2.功能接口中必须只有一个抽象方法吗?
 *          不是的
 *          因为接口中的某些抽象方法,不需要子类实现.
 *          Java中每一个类都继承了Object,如果接口中的抽象方法,可以使用Object类当中的方法作为实现
 *          那么这个抽象方法就无须子类实现
 *
 * 当然,大多数功能接口中,都是只有一个方法,并且是那个要强制子类实现的抽象方法.
 *
 * 功能接口有了以后,就可以使用以下Lambda表达式的语法创建接口的子类对象:
 * Lambda表达式的语法:
 * () -> {}
 * 解释:
 * 1.(),表示功能接口中,那个要强制子类实现的抽象方法的形参列表
 * 说白了抽象方法的形参列表是什么,照抄下来即可!
 *
 * 2.->,是有一个"横杠 + 大于号"组成的一个Lambda表达式运算符
 * 读作"goes to"
 * 它是Lambda表达式的运算符,可以当成一个固定的语法格式
 *
 * 3.{},是重写功能接口中,那个要强制子类实现的抽象方法的重写方法体.
 *
 * 以上说明:
 * 1.Lambda表达式的语法结构,就表明了
 * Lambda表达式最多,且也必须重写一个抽象方法
 * 所以Lambda表达式要求接口必须是功能接口
 * (因为语法结构没有办法重写两个方法)
 *
 * 2.语法结构中的{},是重写方法的方法体
 *  既然是方法体,在方法体中定义的变量是局部变量
 *  说明
 *  Lambda表达式实现的接口子类对象
 *  不可能新增自己的成员,只能重写接口中的抽象方法
 *
 * 直接在局部位置写Lambda表达式的语法,会报错,是什么原因呢?
 * 原因在于直接写语法,编译器无法得知该Lambda表达式,创建的究竟是谁的子类对象
 *
 * 所以这里就缺少一个步骤,需要帮助编译器明确Lambda表达式所创建对象的类型,换句话说,需要告诉编译器
 * Lambda表达式创建的究竟是哪个功能接口的子类对象
 * 这个过程,在java中,称之为"Lambda表达式的类型推断"
 *
 * Lambda表达式的类型推断,如何完成呢?
 * 需要额外的信息,帮助完成推断,根据上下文代码推断
 * 主要有以下四种方式:
 *      1.直接使用父接口的引用指向Lambda表达式
 *          这是最简单,最直接的方式
 *          这种方式使用引用接收对象后,直接用引用操作对象即可.
 *          而且Lambda表达式大括号是重写的方法体
 *          没有自身新增成员
 *          所以也不存在父引用限制访问范围.
 *
 *     2.(仅做了解,是Lambda表达式的特殊语法)
 *     使用类似于强制类型转换的语法
 *     不用引用接收,但是直接告诉编译器类型
 *     ((父接口的名字)(Lambda表达式)).方法名(实参)
 *     并且这种形式,必须直接调用方法,不然会编译报错
 *
 *     以上两种方式,都是非常少见的
 *     更多的时候Lambda表达式的类型推断,需要借助方法完成,以下两种方式更常见:
 *     3.借助方法的返回值类型
 *      如果方法的返回值类型是一个功能接口
 *      那么在方法体中,可以直接用Lambda表达式创建功能接口的子类对象,作为返回值
 *      (因为编译器肯定知道这里需要返回功能接口的子类对象)
 *
 *      4.借助方法的形参列表
 *        如果方法的形参数据类型是一个功能接口
 *        那么调用方法可以直接用Lambda表达式创建功能接口的子类对象,传入方法
 *      (因为编译器肯定知道这里需要传入功能接口的子类对象)
 *
 * 以上就是Lambda表达式的基本使用,但仍然不是最终状态.
 * 接下来重点学习Lambda表达式的简化.
 *
 * @since 14:48
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        // 写接口ITest的子类对象,用Lambda表达式
        ITest it = () -> {
            System.out.println("66666");
        };
        it.test();

        // 写接口ITest2的子类对象,用Lambda表达式
        ITest2 it2 = () -> {
            System.out.println("我是高端的实现!");
        };
        it2.test();


        // 写接口ITest3的子类对象,用Lambda表达式
        System.out.println(((ITest3) ((int a) -> {
            System.out.println("我是用Lambda表达式,创建的ITest3接口的子类对象");
            return a + 100;
        })).test(100));

        method(() -> {
            System.out.println("海的爱太深,时间太浅");
        });

        ITest2 it3 = method();
        it3.test();

    }

    public static ITest2 method() {
        return () -> {
            System.out.println("爱你的心,怎能搁浅!");
        };
    }

    public static void method(ITest it) {
        it.test();
    }
}

@FunctionalInterface
interface ITest {
    void test();
}

@FunctionalInterface
interface ITest2 {
    void test();
}

@FunctionalInterface
interface ITest3 {
    int test(int a);
}


@FunctionalInterface
interface IA {
    void test();

    default void test2() {
    }

    static void test3() {
    }

    /*
        写一个抽象方法,
        该抽象方法可以使用Object类当中的hashCode()方法作为默认实现
        不需要强制子类实现
      */
    int hashCode();

    /*
        写一个抽象方法,
        该抽象方法可以使用Object类当中的equals()方法作为默认实现
        不需要强制子类实现
      */
    boolean equals(Object o);

}